/*
 * Copyright (c) 2009, The MilkyTracker Team.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - Neither the name of the <ORGANIZATION> nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 *  XModule.h
 *  MilkyPlay
 *
 *
 */
#ifndef __XMODULE_H__
#define __XMODULE_H__

#include "XMFile.h"

#define MP_MAXTEXT 32

struct TXMHeader
{
    char sig[17];
    char name[MP_MAXTEXT];
    char whythis1a;
    char tracker[MP_MAXTEXT];
    mp_uword ver;
    mp_uint32 hdrsize;
    mp_uword ordnum;
    mp_uword restart;
    mp_uword channum;
    mp_uword patnum;
    mp_uword insnum;
    mp_uword smpnum;            // additional: number of samples in tune
    mp_uword volenvnum;         // additional: number of volume envelopes
    mp_uword panenvnum;         // additional: number of panning envelopes
    mp_uword frqenvnum;         // additional: number of frequency envelopes (MDL)
    mp_uword vibenvnum;         // additional: number of vibrato envelopes (AMS)
    mp_uword pitchenvnum;       // additional: number of pitch envelopes (IT)
    mp_uword freqtab;
    mp_ubyte uppernotebound;    // additional: note limit if not zero
    mp_sbyte relnote;           // additional: semitone adjust value
    mp_dword flags;             // additional: some flags
    mp_uword tempo;
    mp_uword speed;
    mp_uword mainvol;
    mp_ubyte ord[256];
    mp_ubyte pan[256];
};

// ** BEWARE :) **
// the order of the different elements concerning the envelope info
// is not like in the xm format
struct TEnvelope
{
    mp_uword env[256][2];
    mp_ubyte num, sustain, susloope, loops, loope, type, speed;
};

struct TXMInstrument
{
    enum Flags
    {
        IF_ITNOTEREMAPPING = 0x01,
        IF_ITFADEOUT = 0x02,
        IF_ITENVELOPES = 0x04,
        IF_ITGOBALINSVOL = 0x08
    };

    mp_uint32 size;
    char name[MP_MAXTEXT];
    char type;
    mp_uword samp;
    mp_uint32 shsize;
    mp_sword snum[120];         // -1 is an invalid/empty sample
    mp_uword flags;             // not in .XM => from myself
                                // Also holds NNA (bit 4-5), DCT (bit 6-7), DCA (bit 8-9)
    mp_ubyte notemap[120];      // Impulse Tracker addition
    mp_uword volfade;           // Impulse Tracker addition

    mp_uword venvnum;           // Impulse Tracker envelopes can't be sample-based (different envelopes can map to the same sample) ...
    mp_uword penvnum;           // ... (only when IF_ITENVELOPES flag is set)
    mp_uword fenvnum;
    mp_uword vibenvnum;
    mp_uword pitchenvnum;       // IT pitch envelope

    mp_uword res;               // when bit 3 (= 8) of flags is set, take this as global instrument vol (Impulse Tracker)

    mp_ubyte ifc;               // IT Initial Filter cutoff
    mp_ubyte ifr;               // IT Initial Filter resonance
    //char		extra[20];
};

// some words about the samples:
// although the sample buffer is freely available for acess, I would not recommend
// accessing it directly if you don't exactly know what you are doing because
// to avoid sample clicks when looping samples, it uses a tricky double buffering
// technique between smoothed out sample loop areas and the original sample data
// If you need to modify sample data yourself, use the provided methods
// getSampleValue and setSampleValue and call postProcessSamples when you're done
// modifying the sample, so the loop information is updated correctly
// Also call postProcessSamples when you're changing the loop information
struct TXMSample
{
    private:
        struct TLoopDoubleBuffProps
        {
            enum
            {
                StateUnused,
                StateUsed,
                StateDirty,
            };

            mp_uint32 samplesize;
            mp_ubyte state[4];
            mp_uint32 lastloopend;
        };

        enum
        {
            LoopAreaBackupSize = 4,
            LoopAreaBackupSizeMaxInBytes = 8,
            EmptySize = 8,
            LeadingPadding = sizeof(TLoopDoubleBuffProps) + LoopAreaBackupSizeMaxInBytes + EmptySize,
            TrailingPadding = 16,
            PaddingSpace = LeadingPadding + TrailingPadding
        };

        void restoreLoopArea();
    public:
        mp_uint32 samplen;
        mp_uint32 loopstart;
        mp_uint32 looplen;
        mp_ubyte flags;         // Bit 0: Use volume
                                // Bit 1: Use panning
                                // Bit 2: Use sample volume as channel global volume (.PLM modules)
                                // Bit 3: Use the res field as global sample volume
                                // Bit 4: Use IT style auto vibrato (not implemented yet)
        mp_ubyte vol;
        mp_sbyte finetune;
        mp_ubyte type;          // In Addition to XM: Bit 5: One shot forward looping sample (MOD backward compatibility)
        mp_ubyte pan;
        mp_sbyte relnote;
        mp_uword venvnum;
        mp_uword penvnum;
        mp_uword fenvnum;
        mp_uword vibenvnum;
        mp_uword pitchenvnum;
        mp_ubyte vibtype, vibsweep, vibdepth, vibrate;
        mp_uword volfade;
        mp_ubyte res;           // when bit 3 (= 8) of flags is set, take this as global sample vol (Impulse Tracker)
        mp_sword freqadjust;
        char name[MP_MAXTEXT];
        mp_ubyte terminate;
        mp_sbyte* sample;

        static mp_uint32 getPaddedSize(mp_uint32 size)
        {
            return size + TXMSample::PaddingSpace;
        }

        static mp_ubyte * getPadStartAddr(mp_ubyte* mem)
        {
            return mem - TXMSample::LeadingPadding;
        }

        static mp_ubyte * allocPaddedMem(mp_uint32 size)
        {
            mp_ubyte* result = new mp_ubyte[getPaddedSize(size)];

            if (result == NULL)
                return NULL;

            // clear out padding space
            memset(result, 0, TXMSample::LeadingPadding);
            memset(result + size + TXMSample::LeadingPadding, 0, TXMSample::TrailingPadding);

            TLoopDoubleBuffProps* loopBufferProps = (TLoopDoubleBuffProps*)result;
            loopBufferProps->samplesize = size;

            return result + TXMSample::LeadingPadding;
        }

        static void freePaddedMem(mp_ubyte* mem)
        {
            // behave safely on NULL
            if (mem == NULL)
                return;

            delete[] getPadStartAddr(mem);
        }

        static void copyPaddedMem(void* dst, const void* src, mp_uint32 size)
        {
            mp_ubyte* _src = ((mp_ubyte*)src) - TXMSample::LeadingPadding;
            mp_ubyte* _dst = ((mp_ubyte*)dst) - TXMSample::LeadingPadding;
            memcpy(_dst, _src, getPaddedSize(size));
        }

        static mp_uint32 getSampleSizeInBytes(mp_ubyte* mem)
        {
            TLoopDoubleBuffProps* loopBufferProps = (TLoopDoubleBuffProps*)getPadStartAddr(mem);
            return loopBufferProps->samplesize;
        }

        static mp_uint32 getSampleSizeInSamples(mp_ubyte* mem)
        {
            TLoopDoubleBuffProps* loopBufferProps = (TLoopDoubleBuffProps*)getPadStartAddr(mem);
            return (loopBufferProps->state[1] & 16) ? (loopBufferProps->samplesize >> 1) : loopBufferProps->samplesize;
        }

        void smoothLooping();
        void restoreOriginalState();
        void postProcessSamples();

        // get sample value
        // values range from [-32768,32767] in case of a 16 bit sample
        // or from [-128,127] in case of an 8 bit sample
        mp_sint32 getSampleValue(mp_uint32 index);
        mp_sint32 getSampleValue(mp_ubyte* sample, mp_uint32 index);
        void setSampleValue(mp_uint32 index, mp_sint32 value);
        void setSampleValue(mp_ubyte* sample, mp_uint32 index, mp_sint32 value);

        #ifdef MILKYTRACKER
        bool equals(const TXMSample& sample) const
        {
            if (this->sample != sample.sample)
                return false;

            if (samplen != sample.samplen)
                return false;

            return true;
        }

        bool isMinimizable() const
        {
            return (loopstart + looplen < samplen && (type & 3));
        }
        #endif

        friend class XModule;
};

struct TXMPattern
{
    mp_uint32 len;
    mp_ubyte ptype;
    mp_uword rows;
    mp_ubyte effnum;
    mp_ubyte channum;
    mp_uword patdata;
    mp_ubyte* patternData;

    mp_sint32 compress(mp_ubyte* dest) const;
    mp_sint32 decompress(mp_ubyte* src, mp_sint32 len);
    #ifdef MILKYTRACKER
    bool saveExtendedPattern(const SYSCHAR* fileName) const;
    bool loadExtendedPattern(const SYSCHAR* fileName);

    bool saveExtendedTrack(const SYSCHAR* fileName, mp_uint32 channel) const;
    bool loadExtendedTrack(const SYSCHAR* fileName, mp_uint32 channel);

    const TXMPattern& operator =(const TXMPattern& src);
    #endif
};

//////////////////////////////////////////////////////////////////////////
// This is the class which handles a MilkyPlay module					//
//////////////////////////////////////////////////////////////////////////
class XModule
{
    public:
        enum
        {
            IdentificationBufferSize = 2048,
        };

        ///////////////////////////////////////////////////////
        // this is our loader interface (abstract class)	 //
        // each loader has to conform to this interface		 //
        ///////////////////////////////////////////////////////
        class LoaderInterface
        {
            public:
                // make GCC shut up
                virtual ~LoaderInterface () {
                }
                // returns c-string which identifies the module, NULL if loader can't identify module
                // IMPORTANT: buffer MUST contain eIdentifyBufferSize bytes of the beginning of the file
                virtual const char * identifyModule(const mp_ubyte* buffer) = 0;
                // try to load module (check with identifyModule first)
                virtual mp_sint32 load(XMFileBase& f, XModule* module) = 0;
        };

        enum ModuleTypes
        {
            ModuleType_UNKNOWN,
            ModuleType_669,
            ModuleType_AMF,
            ModuleType_AMS,
            ModuleType_CBA,
            ModuleType_DBM,
            ModuleType_DIGI,
            ModuleType_DSM,
            ModuleType_DSm,
            ModuleType_DTM_1,
            ModuleType_DTM_2,
            ModuleType_FAR,
            ModuleType_GDM,
            ModuleType_GMC,
            ModuleType_IMF,
            ModuleType_IT,
            ModuleType_MDL,
            ModuleType_MOD,
            ModuleType_MTM,
            ModuleType_MXM,
            ModuleType_OKT,
            ModuleType_PLM,
            ModuleType_PSM,
            ModuleType_PTM,
            ModuleType_S3M,
            ModuleType_STM,
            ModuleType_SFX,
            ModuleType_UNI,
            ModuleType_ULT,
            ModuleType_XM,
            ModuleType_NONE = -1,
        };

        class SampleLoader
        {
            protected:
                XMFileBase& f;
            public:
                SampleLoader (XMFileBase& file) :
                    f(file)
                {
                }

                virtual ~SampleLoader ()
                {
                }

                virtual mp_sint32 load_sample_8bits(void* p_dest_buffer, mp_sint32 compressedSize, mp_sint32 p_buffsize) = 0;
                virtual mp_sint32 load_sample_16bits(void* p_dest_buffer, mp_sint32 compressedSize, mp_sint32 p_buffsize) = 0;
        };
    private:
        struct TLoaderInfo
        {
            LoaderInterface* loader;
            ModuleTypes moduleType;
        };
    public:
        enum
        {
            ST_DEFAULT = 0x00,
            ST_DELTA = 0x01,
            ST_UNSIGNED = 0x02,
            ST_16BIT = 0x04,
            ST_PACKING_MDL = 0x08,
            ST_DELTA_PTM = 0x10,
            ST_BIGENDIAN = 0x20,
            ST_PACKING_IT = 0x40,
            ST_PACKING_IT215 = 0x80,
            ST_PACKING_ADPCM = 0x100
        };

        static const mp_sint32 periods[12];
        static const mp_sint32 sfinetunes[16];
        static const mp_sbyte modfinetunes[16];

        static const mp_ubyte numValidXMEffects;
        static const mp_ubyte validXMEffects[];

        //////////////////////////////////////////////////////////////////////////
        // different stuff for importing different module types into own format //
        //////////////////////////////////////////////////////////////////////////
        static mp_sint32 FixedMUL(mp_sint32 a, mp_sint32 b) {
            return ((mp_sint32)(((mp_int64)(a) * (mp_int64)(b)) >> 16));
        }

        ///////////////////////////////////////////////////////
        // convert relative note + finetune into C4 speed    //
        ///////////////////////////////////////////////////////
        static mp_sint32 getc4spd(mp_sint32 relnote, mp_sint32 finetune);
        ///////////////////////////////////////////////////////
        // convert C4 speed into relative note + finetune    //
        ///////////////////////////////////////////////////////
        static void convertc4spd(mp_uint32 c4spd, mp_sbyte* finetune, mp_sbyte* relnote);

        static mp_uint32 amigaPeriodToNote(mp_uint32 period);

        ///////////////////////////////////////////////////////
        // load sample into memory							 //
        ///////////////////////////////////////////////////////
        static bool loadSample(XMFileBase& f, void* buffer,
                               mp_uint32 size, mp_uint32 length,
                               mp_sint32 flags = ST_DEFAULT);

        ///////////////////////////////////////////////////////
        // load a bunch of samples into memory				 //
        ///////////////////////////////////////////////////////
        mp_sint32 loadModuleSample(XMFileBase& f, mp_sint32 index,
                                   mp_sint32 flags8 = ST_DEFAULT, mp_sint32 flags16 = ST_16BIT,
                                   mp_uint32 alternateSize = 0);

        mp_sint32 loadModuleSamples(XMFileBase& f,
                                    mp_sint32 flags8 = ST_DEFAULT, mp_sint32 flags16 = ST_16BIT);

        static void convertXMVolumeEffects(mp_ubyte volume, mp_ubyte& eff, mp_ubyte& op);

        ///////////////////////////////////////////////////////
        // Allocate sample memory and store pointer in pool  //
        // *Note* that this memory is always padded with 16  //
        // bytes at the start *AND* 16 bytes at the end.     //
        ///////////////////////////////////////////////////////
        mp_ubyte * allocSampleMem(mp_uint32 size);

        ///////////////////////////////////////////////////////
        // Free sample memory
        ///////////////////////////////////////////////////////
        void freeSampleMem(mp_ubyte* mem, bool assertCheck = true);

        #ifdef MILKYTRACKER
        void insertSamplePtr(mp_ubyte* ptr);
        void removeSamplePtr(mp_ubyte* ptr);
        #endif

        ///////////////////////////////////////////////////////
        //    Clean up! (Is called before loading a song)    //
        ///////////////////////////////////////////////////////
        bool cleanUp();

        ///////////////////////////////////////////////////////
        // scan through samples and post process to avoid    //
        // interpolation clicks                              //
        ///////////////////////////////////////////////////////
        void postProcessSamples(bool heavy = false);

        ///////////////////////////////////////////////////////
        // set default panning								 //
        ///////////////////////////////////////////////////////
        void setDefaultPanning();
    private:
        // Identify module
        ModuleTypes type;

        // Indicates whether a file is loaded or if it's just an empty song
        bool moduleLoaded;

        // each module comes with it's own sample-memory management (MILKYPLAY_MAXSAMPLES samples max.)
        mp_ubyte* samplePool[MP_MAXSAMPLES];
        mp_uint32 samplePointerIndex;

        // song message retrieving
        char* messagePtr;

        // subsong position table
        mp_ubyte subSongPositions[256 * 2];
        mp_sint32 numSubSongs;

        // add nother envelope to a given list and increase size of array if necessary
        static bool addEnvelope(TEnvelope*& envs, const TEnvelope& env, mp_uint32& numEnvsAlloc, mp_uint32& numEnvs);
        // fix broken envelopes (1 point envelope for example)
        static void fixEnvelopes(TEnvelope* envs, mp_uint32 numEnvs);

        // holds available loader instances
        class LoaderManager
        {
            private:
                TLoaderInfo* loaders;

                mp_uint32 numLoaders;
                mp_uint32 numAllocatedLoaders;

                mp_sint32 iteratorCounter;

                void registerLoader(LoaderInterface* loader, ModuleTypes type);
            public:
                LoaderManager ();
                ~LoaderManager ();

                TLoaderInfo * getFirstLoaderInfo();
                TLoaderInfo * getNextLoaderInfo();
        };

        friend class LoaderManager;

        bool validate();
    public:
        // Module flags
        enum
        {
            MODULE_OLDS3MVOLSLIDES = 1,
            MODULE_ST3NOTECUT = 2,
            MODULE_ST3DUALCOMMANDS = 4,
            MODULE_STMARPEGGIO = 8,
            MODULE_XMARPEGGIO = 16,
            MODULE_XMVOLCOLUMNVIBRATO = 32,
            MODULE_XMNOTECLIPPING = 64,
            MODULE_XMPORTANOTEBUFFER = 128,
            MODULE_AMSENVELOPES = 256,
            MODULE_PTNEWINSTRUMENT = 512,
            MODULE_ST3NEWINSTRUMENT = 1024,
            MODULE_OLDPTINSTRUMENTCHANGE = 2048,
            MODULE_ITNOTEOFF = 4096,
            MODULE_ITNEWEFFECTS = 8192,
            MODULE_ITNEWGXX = 16384,
            MODULE_ITLINKPORTAMEM = 32768,
            MODULE_ITTEMPOSLIDE = 65536,
        };

        enum
        {
            NOTE_LAST = 120,
            NOTE_OFF = 121,
            NOTE_CUT = 122,
            NOTE_FADE = 123,

            SubSongMarkEffect = 0x1E,
            SubSongMarkOperand = 0xFF,
        };

        TXMHeader header;       // module header
        TXMInstrument* instr;   // all instruments (256 of them)
        TXMSample* smp;         // all samples (256 of them, only 255 can be used)
        TXMPattern* phead;      // all pattern headers (256 of them)

        mp_uint32 messageBytesAlloc;
        char* message;          // song message

        TEnvelope* venvs;
        mp_uint32 numVEnvsAlloc;
        mp_uint32 numVEnvs;   // should be equal to header.venvnum
        bool addVolumeEnvelope(const TEnvelope& env) {
            return addEnvelope(venvs, env, numVEnvsAlloc, numVEnvs);
        }

        TEnvelope* penvs;
        mp_uint32 numPEnvsAlloc;
        mp_uint32 numPEnvs;   // should be equal to header.penvnum
        bool addPanningEnvelope(const TEnvelope& env) {
            return addEnvelope(penvs, env, numPEnvsAlloc, numPEnvs);
        }

        TEnvelope* fenvs;
        mp_uint32 numFEnvsAlloc;
        mp_uint32 numFEnvs;   // should be equal to header.fenvnum
        bool addFrequencyEnvelope(const TEnvelope& env) {
            return addEnvelope(fenvs, env, numFEnvsAlloc, numFEnvs);
        }

        TEnvelope* vibenvs;
        mp_uint32 numVibEnvsAlloc;
        mp_uint32 numVibEnvs;   // should be equal to header.vibenvnum
        bool addVibratoEnvelope(TEnvelope& env) {
            return addEnvelope(vibenvs, env, numVibEnvsAlloc, numVibEnvs);
        }

        TEnvelope* pitchenvs;
        mp_uint32 numPitchEnvsAlloc;
        mp_uint32 numPitchEnvs;   // should be equal to header.vibenvnum
        bool addPitchEnvelope(TEnvelope& env) {
            return addEnvelope(pitchenvs, env, numPitchEnvsAlloc, numPitchEnvs);
        }

        ///////////////////////////////////////////////////////
        // convert volume from range [0..64] to [0..255]     //
        ///////////////////////////////////////////////////////
        static mp_sint32 vol64to255(mp_sint32 vol) {
            return ((vol > 64 ? 64 : vol) * 261120 + 65535) >> 16;
        }

        ///////////////////////////////////////////////////////
        // convert volume from range [0..255] to [0..64]     //
        ///////////////////////////////////////////////////////
        static mp_uint32 vol255to64(mp_uint32 vol) {
            return (vol * 64) / 255;
        }

        ///////////////////////////////////////////////////////
        // convert volume from range [0..127] to [0..255]    //
        ///////////////////////////////////////////////////////
        static mp_sint32 vol127to255(mp_sint32 vol) {
            return ((vol > 127 ? 127 : vol) * 131588 + 65535) >> 16;
        }

        ///////////////////////////////////////////////////////
        // convert volume from range [0..128] to [0..255]    //
        ///////////////////////////////////////////////////////
        static mp_sint32 vol128to255(mp_sint32 vol) {
            return ((vol > 128 ? 128 : vol) * 130560 + 65535) >> 16;
        }

        static mp_sint32 pan15to255(mp_sint32 pan) {
            return pan >= 0xF ? 0xFF : (pan << 4);
        }

        ///////////////////////////////////////////////////
        // Allocate necessary memory for song structures //
        ///////////////////////////////////////////////////
        XModule ();

        ///////////////////////////////////////////////////
        // Clean up										//
        ///////////////////////////////////////////////////
        ~XModule ();

        ///////////////////////////////////////////////////
        // Get type of module							 //
        ///////////////////////////////////////////////////
        ModuleTypes getType() const {
            return type;
        }

        ///////////////////////////////////////////////////
        // identify module type                          //
        // IMPORTANT: buffer MUST contain				 //
        // eIdentifyBufferSize bytes from the beginning  //
        // of the file									 //
        ///////////////////////////////////////////////////
        static const char * identifyModule(const mp_ubyte* buffer);

        ///////////////////////////////////////////////////
        // generic module loader						 //
        ///////////////////////////////////////////////////
        mp_sint32 loadModule(XMFileBase& f, bool scanForSubSongs = false);
        mp_sint32 loadModule(const SYSCHAR* fileName, bool scanForSubSongs = false);

        ///////////////////////////////////////////////////
        // Module exporters								 //
        ///////////////////////////////////////////////////
        mp_sint32 saveExtendedModule(const SYSCHAR* fileName);          // FT2 (.XM)
        mp_sint32 saveProtrackerModule(const SYSCHAR* fileName);     // Protracker compatible (.MOD)

        ///////////////////////////////////////////////////
        // module loaded?								 //
        ///////////////////////////////////////////////////
        bool isModuleLoaded() const {
            return moduleLoaded;
        }

        ///////////////////////////////////////////////////
        // string processing							 //
        ///////////////////////////////////////////////////
        static void convertStr(char* strIn, const char* strOut, mp_sint32 nLen, bool filter = true);

        void getTitle(char* str, bool filter = true) const;
        void getSignature(char* str, bool filter = true) const;
        void getTracker(char* str, bool filter = true) const;

        ///////////////////////////////////////////////////
        // dealing with song message				     //
        ///////////////////////////////////////////////////

        // allocate empty song message
        void allocateSongMessage(mp_uint32 initialSize = 512);

        // add one more line of text to the song message
        void addSongMessageLine(const char* line);

        // start iterating text lines (get size of line)
        mp_sint32 getFirstSongMessageLineLength();
        // get next size text line
        mp_sint32 getNextSongMessageLineLength();
        // get line
        void getSongMessageLine(char* line);

        void buildSubSongTable();

        mp_sint32 getNumSubSongs() const {
            return numSubSongs;
        }

        mp_sint32 getSubSongPosStart(mp_sint32 i) const;
        mp_sint32 getSubSongPosEnd(mp_sint32 i) const;

        ///////////////////////////////////////////////////
        // various post processing and analyzing		 //
        ///////////////////////////////////////////////////
        // Remove stupid empty = 0xFE orders
        void removeOrderSkips();
        // Remove unused patterns
        mp_sint32 removeUnusedPatterns(bool evaluate);

        // these are located in ExporterXM.cpp
        mp_sint32 getNumUsedPatterns();
        mp_sint32 getNumUsedInstruments();

        // Analyse various things, for example if there is an old Protracker instrument change
        void postLoadAnalyser();

        // MilkyTracker additions
        void createEmptySong(bool clearPatterns = true, bool clearInstruments = true, mp_sint32 numChannels = 8);

        enum IsPTCompatibleErrorCodes
        {
            IsPTCompatibleErrorCodeNoError = 0,
            IsPTCompatibleErrorCodeTooManyInstruments,
            IsPTCompatibleErrorCodeLinearFrequencyUsed,
            IsPTCompatibleErrorCodeIncompatibleSamples,
            IsPTCompatibleErrorCodeIncompatibleInstruments,
            IsPTCompatibleErrorCodeIncompatiblePatterns
        };

        IsPTCompatibleErrorCodes isPTCompatible();
};

#endif